package com.mapperdb.helper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.mapperdb.builder.annotation.ProviderSqlSourceSupper;
import com.mapperdb.db.ResultEntity;
import com.mapperdb.util.MapperUtils;
import com.mapperdb.util.StrKit;

@Intercepts(@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,
		ResultHandler.class }))
public class ColumnHelper implements Interceptor {
	public static enum Column {
		DENY, ALLOW
	}

	// key --> DENY or ALLOW(default ALLOW)
	static final ThreadLocal<Entry<Column, String>> LOCAL_COLUMNS = new ThreadLocal<Entry<Column, String>>();
	static final Map<String, SqlSource> cacheSqlSourceMap = new HashMap<String, SqlSource>();
	static boolean isStarted = false;

	public static Entry<Column, String> getLocalColumn() {
		if (!isStarted) setLocalColumn(Column.ALLOW, "");
		return LOCAL_COLUMNS.get();
	}

	public static void setLocalColumn(Column column, String columns) {
		LOCAL_COLUMNS.set(newEntry(column, columns));
	}

	public static void setLocalColumnDeny(String columns) {
		LOCAL_COLUMNS.set(newEntry(Column.DENY, columns));
	}

	public static void setLocalColumnAllow(String columns) {
		LOCAL_COLUMNS.set(newEntry(Column.ALLOW, columns));
	}

	static Entry<Column, String> newEntry(Column column, String columns) {
		Map<Column, String> map = new HashMap<Column, String>(1);
		map.put(column, columns);
		return map.entrySet().iterator().next();
	}

	/**
	 * 移除本地变量
	 */
	public static void clearLocalColumn() {
		LOCAL_COLUMNS.remove();
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		isStarted = true;
		final Object[] args = invocation.getArgs();
		//获取原始的ms
		MappedStatement ms = (MappedStatement) args[0];
		// 备份 SqlSource
		SqlSource bak = ms.getSqlSource();
		if (!isProviderSqlSource(bak)) return invocation.proceed();

		MetaObject msObject = SystemMetaObject.forObject(ms);
		try {
			SqlSource sqlSource = processMappedStatement(ms);
			msObject.setValue("sqlSource", sqlSource);
			return invocation.proceed();
		} finally {
			msObject.setValue("sqlSource", bak);
			clearLocalColumn();
		}
	}

	/**
	 * @Title: 处理  MappedStatement
	 * @param ms
	 * @return
	 */
	protected SqlSource processMappedStatement(MappedStatement ms) {
		// 备份 SqlSource
		ProviderSqlSourceSupper sqlSource = (ProviderSqlSourceSupper) ms.getSqlSource();
		ResultEntity resultEntity = sqlSource.getResultEntity();
		Entry<Column, String> entry = ColumnHelper.getLocalColumn();
		if (entry == null) return sqlSource;
		String columns = entry.getValue();
		if (StrKit.isBlank(columns)) return sqlSource;

		// cache
		String key = entry.getKey() + "[" + columns + "]";
		SqlSource ss = cacheSqlSourceMap.get(key);
		if (cacheSqlSourceMap.get(key) == null) {
			synchronized (cacheSqlSourceMap) {
				if (cacheSqlSourceMap.get(key) == null) {
					cacheSqlSourceMap.put(key, ss = getSqlSource(ms, sqlSource, resultEntity, entry, columns));
				}
			}
		}
		return ss;
	}

	protected SqlSource getSqlSource(MappedStatement ms, ProviderSqlSourceSupper sqlSource, ResultEntity resultEntity,
			Entry<Column, String> entry, String columns) {
		Map<String, Result> map = new HashMap<String, Result>();
		for (Result r : resultEntity.results()) {
			map.put(r.column().toUpperCase(), r);
		}

		List<Result> results = null;
		String[] strs = MapperUtils.delimitedStringtoArray(columns);

		if (Column.ALLOW == entry.getKey()) {
			results = new ArrayList<Result>();
			for (String str : strs) {
				String clos = str.toUpperCase();
				if (map.containsKey(clos)) results.add(map.get(clos));
			}
		} else {
			for (String str : strs) {
				String clos = str.toUpperCase();
				if (map.containsKey(clos)) map.remove(clos);
			}
			results = new ArrayList<Result>(map.values());
		}

		resultEntity = new ResultEntity.Builder()//
				.results(results.toArray(ResultEntity.EMPTY_RESULTS))//
				.tableName(resultEntity.tableName())//
				.entityClass(resultEntity.entityClass())//
				.selectKey(resultEntity.selectKey())//
				.resultMapId(resultEntity.resultMapId())//
				.build();

		return new ProviderSqlSourceSupper(ms, resultEntity, sqlSource.getProviderType(), sqlSource.getProviderMethodName());
	}

	/**
	 * @Title: 判断 是否 是 ProviderSqlSourceSupper
	 * @param sqlSource
	 * @return
	 */
	protected boolean isProviderSqlSource(SqlSource sqlSource) {
		if (sqlSource instanceof ProviderSqlSourceSupper) { return true; }
		return false;
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {

	}
}
